﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
using Windows.Foundation;
using Windows.Foundation.Collections;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Controls.Primitives;
using Windows.UI.Xaml.Data;
using Windows.UI.Xaml.Input;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Navigation;
using System.Threading.Tasks;
using Windows.UI.Xaml.Media.Animation;
using Windows.UI.Xaml.Shapes;

using pdftron.PDF;
using PDFRect = pdftron.PDF.Rect;
using PDFDouble = pdftron.Common.DoubleRef;

// The User Control item template is documented at http://go.microsoft.com/fwlink/?LinkId=234236

namespace pdftron.PDF.Tools.Controls
{

    /// <summary>
    /// The annotation list shows all the annotations in a document being viewed by a PDFViewCtrl.
    /// The list will contain any comments that have been add to the annotations and clicking on an
    /// annotation will show it in the PDFViewCtrl.
    /// </summary>
    public sealed partial class AnnotationList : UserControl
    {
        private class AnnotationItem
        {
            public pdftron.PDF.AnnotType AnnotType { get; set; }
            public int PageNumber { get; set; }
            public string Content { get; set; }
            public PDFRect AnnotRectangle { get; set; }
            public string AdditionalInfo { get; set; }

            public AnnotationItem(AnnotType annotType, int pageNumber, string content, PDFRect annotRectangle, string additionalInfo)
            {
                this.AnnotType = annotType;
                this.PageNumber = pageNumber;
                this.Content = content;
                this.AnnotRectangle = annotRectangle;
                this.AdditionalInfo = additionalInfo;
            }
        }



        private static List<AnnotType> WORKABLE_ANNOTATIONS = new List<AnnotType>()
        {
            AnnotType.e_Circle,
            AnnotType.e_Square,
            AnnotType.e_Text,
            AnnotType.e_Line,
            AnnotType.e_Polygon, 
            AnnotType.e_Underline, 
            AnnotType.e_StrikeOut,
            AnnotType.e_Ink,
            AnnotType.e_Highlight,
            AnnotType.e_FreeText,
            AnnotType.e_Squiggly,
            AnnotType.e_Stamp,
            AnnotType.e_Caret,
            AnnotType.e_Polyline,
            AnnotType.e_Redact
        };

        private PDFViewCtrl _PDFViewCtrl;
        private bool _Cancel = false;

        // Store information for future flashing
        private int _LastClickedPageNumber;
        private PDFRect _LastClickedRect;

        // the flashing rectangle
        private Rectangle _FlashingRectangle;

        private bool _FlashOnClick = true;
        /// <summary>
        /// Determines whether a rectangle will flash over the annotation in the PDFViewCtrl when you click a button.
        /// </summary>
        public bool FlashOnClick { get { return _FlashOnClick; } set { _FlashOnClick = value; } }

        private bool _FlashOnClose = false;
        /// <summary>
        /// Determines whether a rectangle will flash over the last clicked annotation in the PDFViewCtrl when you close the menu.
        /// This is more suitable if the view is small.
        /// </summary>
        public bool FlashOnClose { get { return _FlashOnClose; } set { _FlashOnClose = value; } }

        private Brush _FlashBrush;
        /// <summary>
        /// Sets the color of the flashing rectangle
        /// </summary>
        public Brush FlashBrush
        {
            set
            {
                _FlashBrush = value;
                if (_FlashingRectangle != null)
                {
                    _FlashingRectangle.Fill = value;
                }
            }
        }


        /// <summary>
        /// The PDFViewCtrl form which the list should get the document with PDFs.
        /// </summary>
        public PDFViewCtrl PDFViewCtrl { 
            get
            {
                return _PDFViewCtrl;
            }
            set
            {
                _PDFViewCtrl = value;
                if (_PDFViewCtrl != null)
                {
                    _FlashingRectangle = new Rectangle();
                    _FlashingRectangle.Opacity = 0;
                    if (_FlashBrush != null)
                    {
                        _FlashingRectangle.Fill = _FlashBrush;
                    }
                    else
                    {
                        _FlashingRectangle.Fill = this.Resources["AnnotationFlashdBrush"] as SolidColorBrush;
                    }
                    Storyboard.SetTarget(FlashTheRectangle, _FlashingRectangle);
                    Storyboard.SetTargetName(FlashTheRectangle, _FlashingRectangle.Name);
                    //this.PDFViewCtrl.GetAnnotationCanvas().Children.Add(_FlashingRectangle);
                    CreateAnnotationList();
                }
            }
        }

        /// <summary>
        /// Creates a new AnnotationList which is not associated with a PDFViewCtrl.
        /// User the PDFViewCtrl property to activate this control.
        /// </summary>
        public AnnotationList()
        {
            this.InitializeComponent();
        }

        /// <summary>
        /// Constructor that takes a pdfViewCtrl, which should contain the document from which we 
        /// extract annotations.
        /// </summary>
        /// <param name="pdfViewCtrl"></param>
        public AnnotationList(PDFViewCtrl pdfViewCtrl)
        {
            this.InitializeComponent();
            this.PDFViewCtrl = pdfViewCtrl;
        }


        private async void CreateAnnotationList()
        {
            PDFDoc doc = this.PDFViewCtrl.GetDoc();
            if (doc != null)
            {
                try
                {
                    bool gotItems = false;
                    this.PDFViewCtrl.DocLockRead();
                    PageIterator pageIter = doc.GetPageIterator();
                    IList<AnnotationItem> items = await GetNextPageWithAnnotsAsync(pageIter);
                    while (items != null)
                    {
                        gotItems = true;
                        NoAnnotationsTextBlock.Visibility = Windows.UI.Xaml.Visibility.Collapsed;
                        AnnotationStack.Children.Add(MakeAnnotationStack(items));
                        items = await GetNextPageWithAnnotsAsync(pageIter);
                    }
                    if (!gotItems)
                    {
                        NoAnnotationsTextBlock.Visibility = Windows.UI.Xaml.Visibility.Visible;
                    }
                }
                catch (Exception) { }
                finally
                {
                    this.PDFViewCtrl.DocUnlockRead();
                }
            }
        }


        private IAsyncOperation<IList<AnnotationItem>> GetNextPageWithAnnotsAsync(PageIterator pageIter)
        {
            Task<IList<AnnotationItem>> t = new Task<IList<AnnotationItem>>(() =>
            {
                return GetNextPageWithAnnot(pageIter);
            });
            t.Start();
            return t.AsAsyncOperation<IList<AnnotationItem>>();
        }

        private IList<AnnotationItem> GetNextPageWithAnnot(PageIterator pageIter)
        {
            while (pageIter.HasNext() && !_Cancel)
            {
                pdftron.PDF.Page page = pageIter.Current();
                int numAnnots = page.GetNumAnnots();
                if (numAnnots > 0)
                {
                    IList<AnnotationItem> items = GetAnnotationItemsOnPage(page);
                    if (items.Count > 0)
                    {
                        pageIter.Next();
                        return items;
                    }
                }

                pageIter.Next();
            }
            return null;
        }


        private IList<AnnotationItem> GetAnnotationItemsOnPage(pdftron.PDF.Page page)
        {
            List<AnnotationItem> items = new List<AnnotationItem>();
            int numAnnots = page.GetNumAnnots();
            for (int i = 0; i < numAnnots; i++ )
            {
                IAnnot annot = page.GetAnnot(i);
                AnnotType annotType = annot.GetAnnotType();
                if (WORKABLE_ANNOTATIONS.Contains(annotType) && annot.IsMarkup())
                {
                    string author = "";
                    string contents = "";
                    string additonalInfo = "";

                    if (annot is pdftron.PDF.Annots.IMarkup)
                    {
                        Annots.IMarkup markup = (Annots.IMarkup)annot;

                        author = markup.GetTitle();
                        if (author.Length > 0)
                        {
                            author += ": ";
                        }

                        pdftron.PDF.Annots.Popup popup = markup.GetPopup();
                        if (popup != null && popup.IsValid())
                        {
                            contents = popup.GetContents();
                        }
                        else
                        {
                            contents = markup.GetContents();
                        }

                        if (string.IsNullOrWhiteSpace(contents) && annot is Annots.ITextMarkup)
                        {
                            pdftron.PDF.TextExtractor textExtractor = new pdftron.PDF.TextExtractor();

                            textExtractor.Begin(page);
                            String text = textExtractor.GetTextUnderAnnot(annot);
                            if (!string.IsNullOrWhiteSpace(text))
                            {
                                contents = text;
                            }
                        }
                    }

                    // if stamp, see if it's a signature
                    if (annotType == AnnotType.e_Stamp)
                    {
                        pdftron.SDF.Obj obj = annot.GetSDFObj();
                        obj = obj.FindObj(ToolManager.SignatureAnnotationIdentifyingString);
                        if (obj != null)
                        {
                            additonalInfo = "Signature";
                        }
                    }

                    // if a line, see if it is an arrow
                    if (annotType == AnnotType.e_Line)
                    {
                        Annots.Line line = (Annots.Line)annot;
                        Annots.LineEndingStyle endingStyle = line.GetStartStyle();
                        if (endingStyle == Annots.LineEndingStyle.e_ClosedArrow || endingStyle == Annots.LineEndingStyle.e_OpenArrow
                            || endingStyle == Annots.LineEndingStyle.e_RClosedArrow || endingStyle == Annots.LineEndingStyle.e_ROpenArrow)
                        {
                            additonalInfo = "arrow";
                        }
                        endingStyle = line.GetEndStyle();
                        if (endingStyle == Annots.LineEndingStyle.e_ClosedArrow || endingStyle == Annots.LineEndingStyle.e_OpenArrow
                            || endingStyle == Annots.LineEndingStyle.e_RClosedArrow || endingStyle == Annots.LineEndingStyle.e_ROpenArrow)
                        {
                            additonalInfo = "arrow";
                        }
                    }
                    
                    // if a free text, see if it's a callout
                    if (annotType == AnnotType.e_FreeText)
                    {
                        pdftron.SDF.Obj obj = annot.GetSDFObj();
                        obj = obj.FindObj("CL");
                        if (obj != null)
                        {
                            additonalInfo = "Callout";
                        }
                    }

                    AnnotationItem item = new AnnotationItem(annotType, page.GetIndex(), author + contents, annot.GetRect(), additonalInfo);


                    items.Add(item);
                }

            }


            return items;
        }



        private StackPanel MakeAnnotationStack(IList<AnnotationItem> annotItems)
        {
            if (annotItems.Count == 0)
            {
                return null;
            }
            StackPanel pageStack = new StackPanel();
            TextBlock header = new TextBlock();
            header.Text = "Page " + annotItems[0].PageNumber; // they should all be on the same page.
            header.Style = this.Resources["PageHeaderStyle"] as Style;
            pageStack.Children.Add(header);

            StackPanel annotStack = new StackPanel();
            foreach (AnnotationItem item in annotItems)
            {
                Button annotButton = new Button();
                annotButton.Click += annotButton_Click;
                annotButton.Tag = new Tuple<int, PDFRect>(item.PageNumber, item.AnnotRectangle);
                StackPanel contentStack = new StackPanel();
                contentStack.Orientation = Orientation.Horizontal;
                contentStack.HorizontalAlignment = Windows.UI.Xaml.HorizontalAlignment.Left;
                Windows.UI.Xaml.Controls.Image image = new Windows.UI.Xaml.Controls.Image();

                Windows.UI.Xaml.Media.Imaging.BitmapImage bmi = new Windows.UI.Xaml.Media.Imaging.BitmapImage();

                string annotGlyph = new string((char)0x270F, 1);
                switch (item.AnnotType)
                {
                    case AnnotType.e_Line:
                        annotGlyph = new string((char)0xE098, 1);
                        if (!string.IsNullOrWhiteSpace(item.AdditionalInfo) && item.AdditionalInfo.Equals("Arrow", StringComparison.OrdinalIgnoreCase))
                        {
                            annotGlyph = new string((char)0x006F, 1);
                        }
                        break;
                    //caret 0049
                    case AnnotType.e_Square:
                        annotGlyph = new string((char)0xE095, 1);
                        break;
                    case AnnotType.e_Circle:
                        annotGlyph = new string((char)0xE096, 1);
                        break;
                    case AnnotType.e_Ink:
                        annotGlyph = new string((char)0xE235, 1);
                        break;
                    case AnnotType.e_Polygon:
                        annotGlyph = new string((char)0xE097, 1);
                        break;
                    case AnnotType.e_Polyline:
                        annotGlyph = new string((char)0x004E, 1);
                        break;
                    case AnnotType.e_FreeText:
                        annotGlyph = new string((char)0x0050, 1);
                        if (!string.IsNullOrWhiteSpace(item.AdditionalInfo) && item.AdditionalInfo.Equals("Callout", StringComparison.OrdinalIgnoreCase))
                        {
                            annotGlyph = new string((char)0x0049, 1);
                        }
                        break;
                    case AnnotType.e_Text:
                        annotGlyph = new string((char)0xE310, 1);
                        break;
                    case AnnotType.e_Highlight:
                        annotGlyph = new string((char)0x0057, 1);
                        break;
                    case AnnotType.e_Underline:
                        annotGlyph = new string((char)0xE104, 1);
                        break;
                    case AnnotType.e_StrikeOut:
                        annotGlyph = new string((char)0xE105, 1);
                        break;
                    case AnnotType.e_Squiggly:
                        annotGlyph = new string((char)0x0070, 1);
                        break;
                    case AnnotType.e_Caret:
                        annotGlyph = new string((char)0x004F, 1);
                        break;
                    case AnnotType.e_Stamp:
                        annotGlyph = new string((char)0x004A, 1);
                        if (!string.IsNullOrWhiteSpace(item.AdditionalInfo) && item.AdditionalInfo.Equals("Signature", StringComparison.OrdinalIgnoreCase))
                        {
                            annotGlyph = new string((char)0x004D, 1);
                        }
                        break;
                    case AnnotType.e_Redact:
                        annotGlyph = new string((char)0xE208, 1);
                        break;
                }

                //image.Source = bmi;
                Border annotIconBorder = new Border();
                annotIconBorder.Style = this.Resources["AnnotIconBorderStyle"] as Style;

                TextBlock annotIconTextBlock = new TextBlock();
                annotIconTextBlock.Style = this.Resources["AnnotIconTextBlockStyle"] as Style;
                annotIconTextBlock.Text = annotGlyph;
                annotIconBorder.Child = annotIconTextBlock;

                TextBlock comments = new TextBlock();
                comments.Text = item.Content;

                contentStack.Children.Add(annotIconBorder);
                contentStack.Children.Add(comments);

                annotButton.Content = contentStack;
                annotStack.Children.Add(annotButton);
            }
            pageStack.Children.Add(annotStack);
            return pageStack;
        }


        private void annotButton_Click(object sender, RoutedEventArgs e)
        {
            Utilities.AnalyticsHandlerBase.CURRENT.SendEvent("Bookmark", "Navigated by Annotation List");

            Button button = sender as Button;
            if (button != null)
            {
                Tuple<int, PDFRect> tuple = button.Tag as Tuple<int, PDFRect>;
                if (tuple != null)
                {
                    SwitchPageIfNecessary(tuple.Item1);

                    double sx = this.PDFViewCtrl.GetAnnotationCanvasHorizontalOffset();
                    double sy = this.PDFViewCtrl.GetAnnotationCanvasVerticalOffset();

                    PDFDouble x1 = new PDFDouble(tuple.Item2.x1);
                    PDFDouble y1 = new PDFDouble(tuple.Item2.y1);
                    PDFDouble x2 = new PDFDouble(tuple.Item2.x2);
                    PDFDouble y2 = new PDFDouble(tuple.Item2.y2);

                    this.PDFViewCtrl.ConvPagePtToScreenPt(x1, y1, tuple.Item1);
                    this.PDFViewCtrl.ConvPagePtToScreenPt(x2, y2, tuple.Item1);

                    PDFRect canvasRect = new PDFRect(x1.Value + sx, y1.Value + sy, x2.Value + sx, y2.Value + sy);
                    canvasRect.Normalize();

                    ScrollRectIntoView(tuple.Item1, canvasRect);
                    _LastClickedPageNumber = tuple.Item1;
                    _LastClickedRect = canvasRect;
                    if (_FlashOnClick)
                    {
                        FlashRectangle(_LastClickedPageNumber, canvasRect);
                    }


                }
            }
        }
        
        /// <summary>
        /// Will switch page if the current page can not be scrolled into view.
        /// </summary>
        /// <param name="pageNumber"></param>
        private void SwitchPageIfNecessary(int pageNumber)
        {
            // If in a non-continuous mode we want to first go to the page.
            PDFViewCtrlPagePresentationMode mode = this.PDFViewCtrl.GetPagePresentationMode();
            if (mode == PDFViewCtrlPagePresentationMode.e_single_page ||
                mode == PDFViewCtrlPagePresentationMode.e_facing ||
                mode == PDFViewCtrlPagePresentationMode.e_facing_cover)
            {
                int currentPage = PDFViewCtrl.GetCurrentPage();
                if (currentPage == pageNumber)
                {
                    return;
                }

                if (mode == PDFViewCtrlPagePresentationMode.e_facing && ((currentPage + 1) / 2 == (pageNumber + 1) / 2))
                {
                    return;
                }
                else if (mode == PDFViewCtrlPagePresentationMode.e_facing_cover && (currentPage / 2 == pageNumber / 2))
                {
                    return;
                }
                this.PDFViewCtrl.SetCurrentPage(pageNumber);
            }
        }

        private void ScrollRectIntoView(int pageNumber, PDFRect rect)
        {
            // Now, compare the view's position on the canvas to that of the annotation
            double sx = this.PDFViewCtrl.GetAnnotationCanvasHorizontalOffset();
            double sy = this.PDFViewCtrl.GetAnnotationCanvasVerticalOffset();

            PDFRect viewRect = new PDFRect(sx, sy, sx + this.PDFViewCtrl.ActualWidth, sy + this.PDFViewCtrl.ActualHeight);
            PDFRect intersectRect = new PDFRect();
            bool intersect = intersectRect.IntersectRect(viewRect, rect);

            // If we don't intersect enough, scroll it into view.
            if (!intersect || 
                (intersectRect.Width() < 100 && rect.Width() > 100 )|| 
                (intersectRect.Height() < 100 && rect.Height() > 100))
            {
                double x = rect.x1 - ((this.PDFViewCtrl.ActualWidth - rect.Width()) / 2);
                double y = rect.y1 - ((this.PDFViewCtrl.ActualHeight - rect.Height()) / 2);
                if (x < 0)
                {
                    x = 0;
                }
                if (y < 0)
                {
                    y = 0;
                }
                this.PDFViewCtrl.SetHScrollPos(x);
                this.PDFViewCtrl.SetVScrollPos(y);
            }
        }

        /// <summary>
        /// Flashes the rectangle over the annotation
        /// </summary>
        /// <param name="pageNumber"></param>
        /// <param name="rect"></param>
        private void FlashRectangle(int pageNumber, PDFRect rect)
        {
            DetachFlashinfRectangle();
            Canvas canvas = _PDFViewCtrl.GetAnnotationCanvas();
            canvas.Children.Add(_FlashingRectangle);
            _FlashingRectangle.Tag = canvas;
            _FlashingRectangle.Opacity = 0;
            _FlashingRectangle.Width = rect.Width();
            _FlashingRectangle.Height = rect.Height();
            _FlashingRectangle.SetValue(Canvas.LeftProperty, rect.x1);
            _FlashingRectangle.SetValue(Canvas.TopProperty, rect.y1);

            FlashTheRectangle.Begin();
            MakeContentTransparent.Begin();
        }

        private void AnnotationList_Unloaded(object sender, RoutedEventArgs e)
        {
            _Cancel = true;
            if (_FlashOnClose && _LastClickedRect != null)
            {
                FlashRectangle(_LastClickedPageNumber, _LastClickedRect);
            }
        }

        private void FlashingAnimationCompleted(object sender, object e)
        {
            DetachFlashinfRectangle();
        }

        private void DetachFlashinfRectangle()
        {
            Canvas parent = _FlashingRectangle.Tag as Canvas;
            if (parent != null)
            {
                parent.Children.Remove(_FlashingRectangle);
            }
        }
    }
}
